﻿/*
* Copyright 2009-2010 AKABANA.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
* either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
using System;

using System.Reflection;
using System.Reflection.Emit;

using Akabana.Amf.Messaging.Component;
using Akabana.Amf.Util;

namespace Akabana.Amf.Messaging.Service
{
    public class ServiceFactory
    {
        public static bool initialized = false;

        private static ModuleBuilder moduleBuilder;

        private static AssemblyBuilder assemblyBuilder;

        private static AmfConnection connection;

        public static void Initialize(string gatewayUrl)
        {
            InitServicesAssembly();
            if (gatewayUrl == null)
            {
                initialized = false;
            }
            else
            {
                InitAmfConnection(gatewayUrl);
                initialized = true;
            }

        }

        public static object NewInstance(Type serviceInterface)
        {
            var type = CreateServiceType(serviceInterface);
            var result = Activator.CreateInstance(type);

            ((AbstractService)result).amfConnection = connection;

            return result;
        }

        public static Type CreateServiceType(Type serviceInterface)
        {

            var serviceName = serviceInterface.Name.ToLowerCamel();
            var className = serviceInterface.FullName + "Impl";

            var typeBuilder = moduleBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Class, typeof(object), new Type[] { serviceInterface });
            typeBuilder.SetParent(typeof(AbstractService));

            var methodInfos = serviceInterface.GetMethods();

            foreach (var methodInfo in methodInfos)
            {
                CreateServiceMethodDef(typeBuilder, serviceName, methodInfo);
            }

            var result = typeBuilder.CreateType();

            return result;
        }

        private static void CreateServiceMethodDef(TypeBuilder typeBuilder, string serviceName, MethodInfo methodInfo)
        {
            var serviceMethodName = serviceName + "." + methodInfo.Name;
            var parameters = methodInfo.GetParameters();
            var parameterTypes = new Type[parameters.Length];
            for (int i = 0; i < parameters.Length; i++)
            {
                parameterTypes[i] = parameters[i].ParameterType;
            }

            MethodBuilder serviceMethodBuilder =
                typeBuilder
                    .DefineMethod(
                        methodInfo.Name,
                        MethodAttributes.Public | MethodAttributes.Virtual,
                        methodInfo.ReturnType,
                        parameterTypes
                        );


            ILGenerator il = serviceMethodBuilder.GetILGenerator();
            il.DeclareLocal(typeof(string));
            il.DeclareLocal(typeof(object[]));

            var callArgs = il.DeclareLocal(typeof(object[]));

            il.Emit(OpCodes.Ldstr, serviceMethodName);
            il.Emit(OpCodes.Stloc_0);

            var numCallArg = parameterTypes.Length - 2;
            LoadInteger(il, numCallArg);

            il.Emit(OpCodes.Newarr, typeof(object));

            il.Emit(OpCodes.Stloc, callArgs);

            for (int i = 0; i < numCallArg; i++)
            {
                il.Emit(OpCodes.Ldloc, callArgs);

                LoadInteger(il, i);

                LoadArg(il, i + 1);

                if (parameterTypes[i].IsValueType)
                {
                    il.Emit(OpCodes.Box, parameterTypes[i]);
                }

                il.Emit(OpCodes.Stelem_Ref);
            }

            il.Emit(OpCodes.Ldloc, callArgs);
            il.Emit(OpCodes.Stloc_1);

            LoadArg(il, 0);

            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Ldloc_1);

            LoadArg(il, parameterTypes.Length - 1);
            LoadArg(il, parameterTypes.Length);

            var baseCallMethodInfo = typeBuilder.BaseType.GetMethod("Call", BindingFlags.NonPublic | BindingFlags.Instance);
            il.EmitCall(OpCodes.Call, baseCallMethodInfo, new Type[] { typeof(string), typeof(object[]), typeof(object), typeof(object) });

            il.Emit(OpCodes.Ret);
        }

        private static void LoadInteger(ILGenerator il, int i)
        {
            switch (i)
            {
                case 0: il.Emit(OpCodes.Ldc_I4_0); break;
                case 1: il.Emit(OpCodes.Ldc_I4_1); break;
                case 2: il.Emit(OpCodes.Ldc_I4_2); break;
                case 3: il.Emit(OpCodes.Ldc_I4_3); break;
                case 4: il.Emit(OpCodes.Ldc_I4_4); break;
                case 5: il.Emit(OpCodes.Ldc_I4_5); break;
                case 6: il.Emit(OpCodes.Ldc_I4_6); break;
                case 7: il.Emit(OpCodes.Ldc_I4_7); break;
                case 8: il.Emit(OpCodes.Ldc_I4_8); break;
                case -1: il.Emit(OpCodes.Ldc_I4_M1); break;
                default:
                    if (-128 <= i && i <= 127)
                    {
                        il.Emit(OpCodes.Ldc_I4_S, i);
                    }
                    else
                    {
                        il.Emit(OpCodes.Ldc_I4, i);
                    }
                    break;
            }
        }

        private static void LoadArg(ILGenerator il, int i)
        {
            if (i < 4)
            {
                switch (i)
                {
                    case 0:
                        il.Emit(OpCodes.Ldarg_0);
                        break;
                    case 1:
                        il.Emit(OpCodes.Ldarg_1);
                        break;
                    case 2:
                        il.Emit(OpCodes.Ldarg_2);
                        break;
                    case 3:
                        il.Emit(OpCodes.Ldarg_3);
                        break;
                }
            }
            else
            {
                il.Emit(OpCodes.Ldarg_S, i);
            }
        }

        private static void InitServicesAssembly()
        {
            var domain = AppDomain.CurrentDomain;
            var assemblyName = new AssemblyName();

            assemblyName.Name = "ServicesAssembly.dll";
            assemblyBuilder = domain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
            moduleBuilder = assemblyBuilder.DefineDynamicModule("ServicesModule");
        }

        private static void InitAmfConnection(string uri)
        {
            var gatewayUri = new Uri(uri);
            connection = new AmfConnection();
            connection.Connect(gatewayUri);
        }
    }
}
